<script lang="ts" setup>
import { apiMenu } from "@/api/sys";
import { MenuType, type AdvancedSearch, type MenuInfo } from "@/models";
import type { CheckboxValueType, ElTree } from "element-plus";
import { reactive, ref, watch, type UnwrapRef } from "vue";

type CheckedInfo = { checkedNodes: MenuInfo[]; checkedKeys: number[]; halfCheckedNodes: MenuInfo[]; halfCheckedKeys: number[] };
type State = {
  tree: MenuInfo[];
  flat: MenuInfo[];
  fullChecked: number[];
  halfChecked: number[];
  actsChecked: UnwrapRef<Record<number, number[]>>;
  readonly: boolean;
};
const state = reactive<State>({ tree: [], flat: [], fullChecked: [], halfChecked: [], actsChecked: {}, readonly: false });
const props = defineProps<{ modelValue?: number[]; readonly?: Boolean }>();
const emits = defineEmits<{ (event: "update:modelValue", value?: number[]): void }>();
const treeRef = ref<InstanceType<typeof ElTree>>();
const { 信息, 功能, 子系统, 目录, 页面 } = MenuType;

watch(
  () => props.modelValue,
  async (newVal) => {
    if (!state.flat.length) {
      await load();
    }
    setCheckedState(newVal);
  },
  { immediate: true },
);

watch(
  () => props.readonly,
  (newVal) => {
    if (newVal !== undefined) state.readonly = newVal.valueOf();
  },
  { immediate: true },
);

async function load() {
  const search: AdvancedSearch<MenuInfo> = { s: [{ f: "sortId", s: "ASC" }] };
  const response = await apiMenu.find<MenuInfo>(search);
  if (!response.value) return;

  const menus: MenuInfo[] = Array.from(response.value);
  for (const menu of menus) {
    let parent = menus.find((p) => p.id === menu.pid);
    if (!parent) {
      menu.pid = undefined;
      continue;
    }

    if (menu.type === 功能) {
      if (!parent.actions) parent.actions = [];
      parent.actions.push(menu);
    } else {
      if (!parent.children) parent.children = [];
      parent.children.push(menu);
    }
  }

  state.tree = menus.filter((r) => r.pid === null || r.pid === undefined);
  state.flat = menus;
}

function setCheckedState(ids?: number[]) {
  const $tree = treeRef.value;
  if (!$tree) return;

  state.fullChecked = [];
  state.halfChecked = [];
  state.actsChecked = {};

  if (ids === undefined || ids.length === 0) return;

  for (const id of ids) {
    const menu = state.flat.find((m) => m.id === id);
    if (!menu) continue;

    switch (menu.type) {
      case 子系统:
      case 目录:
        if (menu.children && menu.children.length > 0) {
          const cids = menu.children.map((c) => c.id);
          if (cids.every((c) => ids.includes(c))) {
            state.fullChecked.push(id);
          } else {
            state.halfChecked.push(id);
          }
        } else {
          // 容器类菜单如果不包含子级，则取消勾选
        }
        break;
      case 页面:
      case 信息:
        state.fullChecked.push(id);
        break;
      case 功能:
        if (menu.pid) {
          if (!state.actsChecked[menu.pid]) state.actsChecked[menu.pid] = [];
          state.actsChecked[menu.pid].push(id);
        }
        break;
      default:
        break;
    }
  }

  $tree.setCheckedKeys(state.fullChecked);
}

function treeCheck(menu: MenuInfo, checked: CheckedInfo) {
  cascadeCheck(menu, checked);
  state.fullChecked = checked.checkedKeys;
  state.halfChecked = checked.halfCheckedKeys;
  doEmits();
}

function cascadeCheck(menu: MenuInfo, checked: CheckedInfo) {
  if (menu.type === 页面 && menu.actions && menu.actions.length) {
    if (checked.checkedKeys.includes(menu.id)) {
      state.actsChecked[menu.id] = menu.actions.map((a) => a.id);
    } else {
      state.actsChecked[menu.id] = [];
    }
  }

  if ((menu.type === 子系统 || menu.type === 目录) && menu.children && menu.children.length) {
    menu.children.forEach((c) => cascadeCheck(c, checked));
  }
}

function actsChange(parent: MenuInfo, val: CheckboxValueType[]) {
  if (val.length === 0) {
    state.actsChecked[parent.id].length = 0;
  } else {
    if (!state.fullChecked.includes(parent.id)) {
      state.fullChecked.push(parent.id);
      treeRef.value?.setChecked(parent.id, true, false);
    }
  }
  doEmits();
}

function doEmits() {
  const ids: number[] = [];
  ids.push(...state.halfChecked);
  ids.push(...state.fullChecked);
  Object.keys(state.actsChecked).forEach((k) => {
    const acts = Reflect.get(state.actsChecked, k);
    if (acts && acts.length > 0) ids.push(...acts);
  });

  emits("update:modelValue", ids);
}
</script>

<template>
  <el-tree
    ref="treeRef"
    class="menu-picker"
    node-key="id"
    :data="state.tree"
    :props="{ label: 'title', disabled: () => state.readonly }"
    :expand-on-click-node="false"
    show-checkbox
    @check="treeCheck"
  >
    <template #default="{ data }">
      <el-row class="node-panel">
        <el-col :span="5">
          <icon-symbol class="m-r-1" v-if="!!data.icon" :name="data.icon" />
          <span v-text="data.title"></span>
        </el-col>
        <el-col :span="19">
          <el-checkbox-group v-if="!!data.actions" v-model="state.actsChecked[data.id]" :disabled="state.readonly" @change="(val) => actsChange(data, val)">
            <el-tag v-for="{ id, title, icon } in data.actions" :key="id">
              <el-checkbox :label="id">
                <icon-symbol class="m-r-1" v-if="!!icon" :name="icon" />
                <span v-text="title"></span>
              </el-checkbox>
            </el-tag>
          </el-checkbox-group>
        </el-col>
      </el-row>
    </template>
  </el-tree>
</template>

<style lang="scss" scoped>
.menu-picker {
  border: 1px solid var(--vxe-input-border-color);
  border-radius: var(--vxe-border-radius);
  padding: 2px;

  :deep(.el-tree-node) {
    white-space: unset;
  }

  :deep(.el-tree-node__content) {
    height: auto;
    min-height: var(--el-tree-node-content-height);
  }

  .el-tag {
    margin-right: 0.25rem;
    padding: 0 0.25rem;
  }

  .el-checkbox {
    :deep(.el-checkbox__label) {
      padding-left: 0.25rem;
    }
  }

  :deep(.el-checkbox__input.is-disabled + span.el-checkbox__label) {
    color: inherit;
  }
  :deep(.el-checkbox__input.is-checked + span.el-checkbox__label) {
    color: var(--el-checkbox-checked-text-color);
  }

  :deep(.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner) {
    border-color: var(--el-checkbox-checked-input-border-color);
    background-color: var(--el-checkbox-checked-bg-color);
  }

  :deep(.el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner) {
    border-color: var(--el-checkbox-checked-input-border-color);
    background-color: var(--el-checkbox-checked-bg-color);
  }

  :deep(.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner::after) {
    transform: rotate(45deg) scaleY(1);
    border-color: var(--el-checkbox-checked-icon-color);
  }

  :deep(.el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner::before) {
    border-color: var(--el-checkbox-checked-icon-color);
    background-color: var(--el-checkbox-checked-icon-color);
  }

  .node-panel {
    align-items: center;
    width: 100%;
  }

  .el-checkbox-group {
    margin: 2px 0;
    border: 1px dashed #aaaaaa80;
    padding: 2px;
  }
}
</style>
